import time
import math
from typing import Callable, Any

from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtGui import QMouseEvent, QKeyEvent, QImage, QPixmap, QPainter
from PyQt5.QtWidgets import QLabel, QMessageBox, QApplication, QGraphicsView, QGraphicsScene, QGraphicsPixmapItem
from adbutils import adb

from .scrcpy.core import Client
from .scrcpy.const import *


class ScrcpyThread(QThread):
    frame_received = pyqtSignal(object)

    def __init__(self, client: Client):
        super().__init__()
        self.client = client

    def run(self):
        self.client.add_listener(EVENT_FRAME, self.frame_received.emit)
        self.client.start()


class ScrcpyView(QLabel):

    def __init__(self):
        super().__init__()
        self.width = self.width()
        self.setAlignment(Qt.AlignAbsolute)

        self.alive = True

        # 启动 scrcpy 客户端线程
        self.client = Client()
        self.scrcpy_thread = ScrcpyThread(self.client)
        self.scrcpy_thread.frame_received.connect(self.on_frame)
        self.scrcpy_thread.start()

        # 捕捉鼠标事件
        self.setMouseTracking(True)
        self.mousePressEvent = self.on_mouse_event(ACTION_DOWN)
        self.mouseMoveEvent = self.on_mouse_event(ACTION_MOVE)
        self.mouseReleaseEvent = self.on_mouse_event(ACTION_UP)

        # 捕捉键盘事件
        self.keyPressEvent = self.on_key_event(ACTION_DOWN)
        self.keyReleaseEvent = self.on_key_event(ACTION_UP)

    def choose_device(self, device):
        if device not in self.devices:
            msgBox = QMessageBox()
            msgBox.setText(f"Device serial [{device}] not found!")
            msgBox.exec()
            return

        # Ensure text
        self.ui.combo_device.setCurrentText(device)
        # Restart service
        if getattr(self, "client", None):
            self.client.stop()
            self.client.device = adb.device(serial=device)

    def list_devices(self):
        self.ui.combo_device.clear()
        items = [i.serial for i in adb.device_list()]
        self.ui.combo_device.addItems(items)
        return items

    def on_flip(self, _):
        self.client.flip = self.ui.flip.isChecked()

    def on_click_home(self):
        self.client.control.keycode(KEYCODE_HOME, ACTION_DOWN)
        self.client.control.keycode(KEYCODE_HOME, ACTION_UP)

    def on_click_back(self):
        self.client.control.back_or_turn_screen_on(ACTION_DOWN)
        self.client.control.back_or_turn_screen_on(ACTION_UP)

    def on_mouse_event(self, action=ACTION_DOWN):
        def handler(evt: QMouseEvent):
            focused_widget = QApplication.focusWidget()
            if focused_widget is not None:
                focused_widget.clearFocus()
            resolution = self.client.resolution
            if resolution:
                ratio = self.width / max(resolution)
                self.client.control.touch(evt.x() / ratio, evt.y() / ratio, action)

        return handler

    def on_key_event(self, action=ACTION_DOWN):
        def handler(evt: QKeyEvent):
            code = self.map_code(evt.key())
            if code != -1:
                self.client.control.keycode(code, action)

        return handler

    def on_frame(self, frame):
        # app.processEvents()
        if frame is not None:
            ratio = self.width / max(self.client.resolution)
            image = QImage(
                frame,
                frame.shape[1],
                frame.shape[0],
                frame.shape[1] * 3,
                QImage.Format_BGR888,
            )
            pix = QPixmap(image)
            pix.setDevicePixelRatio(1 / ratio)
            self.setPixmap(pix)
            # self.resize(1, 1) 导致闪烁，暂不设置

    def closeEvent(self, _):
        self.client.stop()
        self.alive = False


class ScrcpyGraphicsView(QGraphicsView):
    is_recording: bool = False
    click_listener: Callable[..., Any]
    moved_listener: Callable[..., Any]
    _dx, _dy, _d_time = 0, 0, 0
    _is_confirm_move: bool = True

    def __init__(self):
        super().__init__()
        self.width = self.width()
        scene = QGraphicsScene(self)
        self.setScene(scene)

        # 启动 scrcpy 客户端线程
        self.client = Client()
        self.scrcpy_thread = ScrcpyThread(self.client)
        self.scrcpy_thread.frame_received.connect(self.on_frame)
        self.scrcpy_thread.start()

        # 捕捉鼠标事件
        self.setMouseTracking(True)
        self.mousePressEvent = self.on_mouse_event(ACTION_DOWN)
        self.mouseMoveEvent = self.on_mouse_event(ACTION_MOVE)
        self.mouseReleaseEvent = self.on_mouse_event(ACTION_UP)

        # 捕捉键盘事件
        self.keyPressEvent = self.on_key_event(ACTION_DOWN)
        self.keyReleaseEvent = self.on_key_event(ACTION_UP)

    def on_mouse_event(self, action=ACTION_DOWN):
        def handler(evt: QMouseEvent):
            pos = evt.pos()  # 获取鼠标按下的位置
            scene_pos = self.mapToScene(pos)  # 转换为场景坐标
            if self.is_recording:
                self.on_record_mouse_event(action, scene_pos)
            else:
                x, y = scene_pos.x(), scene_pos.y()
                self.client.control.touch(x, y, action)

        return handler

    # 随意写一下
    def on_record_mouse_event(self, action, scene_pos):
        x, y = scene_pos.x(), scene_pos.y()
        if action == ACTION_DOWN:
            print(f"Mouse pressed at: {scene_pos}")
            self._dx, self._dy = x, y
            self._is_confirm_move = False
            self._d_time = time.time()
            return
        elif action == ACTION_MOVE:
            if not self._is_confirm_move:
                delta_x = x - self._dx
                delta_y = y - self._dy
                if math.sqrt(delta_x * delta_x + delta_y * delta_y) > 8:
                    self.client.control.touch(x, y, ACTION_DOWN)
                    self._is_confirm_move = True
            self.client.control.touch(x, y, action)
        elif action == ACTION_UP:
            print(f"Mouse released at: {scene_pos}")
            delta_x = x - self._dx
            delta_y = y - self._dy
            if math.sqrt(delta_x * delta_x + delta_y * delta_y) <= 8:
                if self.click_listener:
                    self.click_listener(x, y)
                self.client.control.touch(x, y, ACTION_DOWN)
            else:
                if self.moved_listener:
                    duration = time.time() - self._d_time
                    self.moved_listener(self._dx, self._dy, x, y, duration)
            self.client.control.touch(x, y, action)
            self._dx, self._dy = 0, 0

    def on_key_event(self, action=ACTION_DOWN):
        def handler(evt: QKeyEvent):
            code = map_code(evt.key())
            if code != -1:
                self.client.control.keycode(code, action)

        return handler

    def on_frame(self, frame):
        """接收 scrcpy 的屏幕帧并在 QGraphicsView 中显示"""
        if frame is not None:
            # 从 numpy.ndarray 获取宽度和高度
            height, width, channels = frame.shape
            # 将帧数据转换为 QImage
            img = QImage(frame.data, width, height, width * channels, QImage.Format_BGR888)
            # 将 QImage 转换为 QPixmap
            pixmap = QPixmap(img)
            # 将 QPixmap 显示在 QGraphicsView 中
            scene = self.scene()
            scene.clear()  # 清除之前的图像
            # 创建一个 QGraphicsPixmapItem 以适应视图
            pixmap_item = QGraphicsPixmapItem(pixmap)
            scene.addItem(pixmap_item)

            # 调整视图大小以适应投屏内容
            self.setRenderHint(QPainter.Antialiasing)
            self.fitInView(pixmap_item, Qt.KeepAspectRatio)
            self.setScene(scene)

    def closeEvent(self, _):
        self.client.stop()


def map_code(code):
    """
    Map qt keycode ti android keycode

    Args:
        code: qt keycode
        android keycode, -1 if not founded
    """

    if code == -1:
        return -1
    if 48 <= code <= 57:
        return code - 48 + 7
    if 65 <= code <= 90:
        return code - 65 + 29
    if 97 <= code <= 122:
        return code - 97 + 29

    hard_code = {
        32: KEYCODE_SPACE,
        16777219: KEYCODE_DEL,
        16777248: KEYCODE_SHIFT_LEFT,
        16777220: KEYCODE_ENTER,
        16777217: KEYCODE_TAB,
        16777249: KEYCODE_CTRL_LEFT,
    }
    if code in hard_code:
        return hard_code[code]

    print(f"Unknown keycode: {code}")
    return -1
